/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFParaManager
  \brief a singleton manager class to manage all kinds of parameters globally,
  typically channel id maps, detector positions, calibration constants, and other
  miscellaneous ones.
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-12-03
  \date 2023-05-27 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-12-03   <td>Asia Sun    <td>file created                  </tr>
  <tr>  <td>2023-05-27   <td>Asia Sun    <td>getopt added                  </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <errno.h>
#include <unistd.h>
#include <iostream>
#include <climits>
#include <TFile.h>
#include <TH1F.h>
#include "ETFParaManager.h"
#include "ETFAnalyzer.h"
#include "ETFOnlineSource.h"
#include "ETFOfflineSource.h"
#include "ETFClient.h"
#include "ETFYaml.h"
#include "ETFMath.h"
#include "ETFMsg.h"

using std::cout;
using std::endl;

#define er ETFMsg::Error

static const string ROOTDIR = "../etfana/"; // root directory for config files

ETFParaManager *ETFParaManager::fInstance = nullptr;

ETFParaManager::ETFParaManager() : fChIdConfig(nullptr), fDetPosConfig(nullptr),
fDelayConfig(nullptr), fMisConfig(nullptr), fOnlineConfig(nullptr),
fDetMaskConfig(nullptr), fIndex0(0), fIndex1(UINT_MAX), fAnaLevel(-2),
fIs3DTrk(false), fIsReset(false), fEXE("./ana"), fMagB(-999.), fRunId(-1),
fRTFile(0){
  ReadConfigFiles();
} // end ctor

ETFParaManager::~ETFParaManager(){
  if(fChIdConfig) delete fChIdConfig;
  if(fDetPosConfig) delete fDetPosConfig;
  if(fDelayConfig) delete fDelayConfig;
  if(fMisConfig) delete fMisConfig;
  if(fOnlineConfig) delete fOnlineConfig;
} // end dtor

// check if p is null, and issue a warning if so
const ETFYaml *ETFParaManager::testnull(const ETFYaml *p){
  if(!p) er("ETFParaManager", "testnull: input pointer is nullptr");
  return p;
} // end member function testnull

ETFParaManager *ETFParaManager::Instance(){
  if(!fInstance) fInstance = new ETFParaManager();
  return fInstance;
} // end member function Instance

/// parse commandline inputs
void ETFParaManager::GetOpt(int argc, char *argv[]){
  if(1 == argc){
    PromptHelp();
    exit(EXIT_SUCCESS);
  } // end if
  int ch;
  while((ch = getopt(argc, argv, "i:f:dt::m:s:u::phvc")) != -1){
    switch(ch){
      case 'i': fIndex0 = atoi(optarg); break;
      case 'f': fIndex1 = atoi(optarg); break;
      case 'd': fAnaLevel = -1; break;
      case 't':
        fAnaLevel = 1;
        if(optarg){
          if(3 == atoi(optarg)) fIs3DTrk = true;
          else PromptHelp();
        } // end if
        break;
      case 'm': fMagB = atof(optarg); break;
      case 's': fDrawVec.push_back(atoi(optarg)); break;
			case 'u': fRunId = 1; if(optarg) fRunId = atoi(optarg); break;
      case 'p': fAnaLevel = 2; break;
      case 'h': PromptHelp(); exit(EXIT_SUCCESS);
      case 'v': ETFMsg::Logo(); exit(EXIT_SUCCESS);
      case 'c':
        cout << "The config dir is \033[1m" << fConfigDir << endl;
        exit(EXIT_SUCCESS);
      default:
        cout << "Illegal input." << endl;
        PromptHelp(); exit(EXIT_SUCCESS);
    } // end switch
  } // end while
  if(fIndex0 < 0 || fIndex1 < 0){ fIndex0 = 0; fIndex1 = UINT_MAX; }

  // read the data files
  const int cnt = argc - optind;
  if(!cnt) er("ETFParaManager", "GetOpt: no data file specified");
  // collect all the raw data files
  for(int i = 0; i < cnt; i++){
    const char *f = argv[optind + i];
    if(ETFMath::endwith(f, ".root")){
      ETFMsg::Info("ETFParaManager", "ROOT File input detected: %s", f);
      ETFMsg::Info("ETFParaManager", "For post-ana analysis, please use `./gana <rootfile>`");
      if(-1 == system(ETFMsg::Form("./gana %s", f)))
        er("ETFParaManager", "GetOpt: %s", strerror(errno));
      exit(1);
      // continue;
    } // end if
    fDataFiles.push_back(f);
  } // end for
} // end member function GetOpt

// exe: the name of the executable
void ETFParaManager::PromptHelp() const{
	cout << "\n          ----------------- USER MANUL ----------------\n";
	cout << "Usage:\n";
	cout << "\t./ana [pxifile] [vmefile] ";
	cout << "[-\033[1mi\033[0mev_start] [-\033[1mf\033[0mev_stop]\n";
	cout << "[-\033[1md\033[0m] ";
	cout << "[-\033[1mt\033[0m][3] ";
	cout << "[-\033[1mu\033[0m][runId] ";
	cout << "[-\033[1mm\033[0mMag(in Tesla)] ";
	cout << "[-\033[1ms\033[0m]evDrawn ";
	cout << "[-\033[1mp\033[0m] ";
	cout << "[-\033[1mh\033[0m] [-\033[1mv\033[0m]\n [-\033[1mc\033[0m]\n";

  string vf = "20230421.001", pf = " 20230421_1652.dat";
  cout << "\nExamples:" << endl;
  cout << "(NOTE that NO data files are needed when running in ONLINE MODE)" << endl;
  cout << "(using parameters in <sourcedir>/config/<exp>/mis.yaml)" << endl;
  cout << "(where <exp> is specified in <sourcedir>/config/exp.yaml)" << endl;
  cout << fEXE << pf << " " << vf << " // PXI+VME analysis" << endl;
  cout << fEXE << vf << " " << pf <<  " // same as above" << endl;
  cout << fEXE << pf << " -i0 -f100 // analyze event [0~100)" << endl;
  cout << "    /* (analyze all events by default) */\n";
  cout << fEXE << pf << " -d // only decoding;" << endl;
  cout << fEXE << pf << " -t // 2D tracking;" << endl;
  cout << fEXE << pf << " -t3 // 3D tracking;" << endl;
  cout << fEXE << pf << " -m1.2 // 1.2 Tesla for the dipole magnet;" << endl;
  cout << fEXE << pf << " -s325 // draw event #325;" << endl;
  cout << fEXE << pf << " -u1 // <rawfile>_1.root;" << endl;
  cout << fEXE << pf << " -p // PID;" << endl;
  cout << fEXE << " -h // print this help list;" << endl;
  cout << fEXE << " -v // print the version info of \033[36;1mETFAna\033[0m;" << endl;
  cout << fEXE << " -h // print the config file directory;" << endl;
  cout << endl;
} // end member function PromptHelp

void ETFParaManager::ReadConfigFiles(){
  // get the base directory //
  string f = ROOTDIR + "config/exp.yaml";
  // string f = "config/exp.yaml";
  if(access(f.c_str(), F_OK))
    er("ETFParaManager", "ReadConfigFiles: %s non-existent", f.c_str());
  ETFYaml cc(f);
  if(cc["basedir"]) fConfigDir = ROOTDIR + "config/" + cc["basedir"].as<string>() + "/";
  else er("ETFParaManager", "ReadConfigFiles: basedir empty");

  if(!SetChIdConfigFile("chIdMap.yaml")) er("ETFParaManager", "ReadConfigFiles: chidMap.yaml not found");
  if(!SetDetPosConfigFile("detPos.yaml")) er("ETFParaManager", "ReadConfigFiles: detPos.yaml not found");
  if(!SetDelayConfigFile("delay.yaml")) er("ETFParaManager", "ReadConfigFiles: delay.yaml not found");
  if(!SetMisConfigFile("mis.yaml")) er("ETFParaManager", "ReadConfigFiles: mis.yaml not found");
  const ETFYaml &c = *fMisConfig;
  // update [ev0, ev1), anaLevel and is3DTrk with mis.yaml //
  if(c["ev"]){
    fIndex0 = c["ev"][0].as<unsigned>();
    fIndex1 = c["ev"][1].as<unsigned>();
  } // end if
  if(fIndex1 <= fIndex0)
    er("ETFParaManager", "ReadConfigFiles: illegal [ev_start~ev_stop): [%d~%d)",
      fIndex0, fIndex1);
  fAnaLevel = c["anaLevel"].as<int>();
  if(false == fIs3DTrk) fIs3DTrk = c["is3DTracking"].as<bool>() && (fAnaLevel >= 1);

  if(c["debug"]) ETFMsg::SetDebug(c["debug"][0].as<bool>()); // general debug info
  // set magnetic intensity - terminal input first
  if(-999. == fMagB && c["magB"]) fMagB = c["magB"].as<double>();

  SetOnlineConfigFile("online.yaml");
  SetDetMaskConfigFile("detmask.yaml");
  if(fDetMaskConfig && (*fDetMaskConfig)["detmask"])
    for(const auto &d : (*fDetMaskConfig)["detmask"]) fDetMask.push_back(d.as<string>());

  // read the R(t) histos //
  const char *rtf = "rt.root";
  if(!access(rtf, F_OK)) fRTFile = new TFile(rtf);
} // end member function ReadConfigFiles

bool ETFParaManager::Disabled(const string &name) const{
  for(const auto &d : fDetMask) if(name == d) return true;
  return false;
} // end member function Disabled

// reload to include changes in online.yaml
void ETFParaManager::ReloadOnlineConfig(){
  if(fOnlineConfig){ delete fOnlineConfig; fOnlineConfig = nullptr; }
  SetOnlineConfigFile("online.yaml");
} // end member function ReloadOnlineConfig

bool ETFParaManager::SetConfigFile(ETFYaml *&c, const string &file){
  string f = fConfigDir + file;
  if(c) er("ETFParaManager", "SetConfigFile: input yaml not nullptr");
  if(access(f.c_str(), F_OK)){
    ETFMsg::Info("ETFParaManager", "SetConfigFile: file %s non-existent", f.c_str());
    return false;
  } // end if
  c = new ETFYaml(f);
  return true;
} // end member function SetConfigFile

void ETFParaManager::ConfigOfflineSource(ETFOfflineSource *s){
  const ETFYaml &c = *GetMisPara();
  if(c["debug"]) s->SetDebug(c["debug"][1].as<bool>());
  for(const auto &f : fDataFiles) s->AddDataFile(f);
} // end member function ConfigOfflineSource

void ETFParaManager::ConfigOnlineSource(ETFOnlineSource *s){
  // add daq sources //
  const ETFYaml cc("ipc.yaml");
  if(!cc["daq"]){
    cout << "\033[31;1mno daq items in ipc.yaml\033[0m" << endl;
    exit(1);
  } // end if
  const int nshm = cc["nshm"] ? cc["nshm"].as<int>() : 0; // nof shms out
  for(auto &p : cc["daq"]){
    const bool t = p["type"].as<bool>();
    const DaqType type = t ? kPXI : kVME;
    const string ip = p["ipport"][0].as<string>();
    const short port = p["ipport"][1].as<short>();
    ETFClient *c = new ETFClient(ip, port, type, nshm);
    if(c->Connected()) s->AddDaqSource(c);
    else delete c;
  } // end for over daqs
  if(!s->GetNofUnpackers()){
    cout << "\033[31;1mNo Client is established\033[0m" << endl;
    exit(1);
  } // end if
} // end member function ConfigOnlineSource

void ETFParaManager::ConfigAnalyzer(ETFAnalyzer *a){
  const ETFYaml &c = *GetMisPara();
  ////// set debug //////
  if(c["debug"]){
    const ETFYaml &d = c["debug"];
    a->SetDetDebug(d[2].as<bool>());
    a->SetTrackingDebug(d[3].as<bool>());
    a->SetPIDDebug(d[4].as<bool>());
  } // end if
  //////////   set analysis depth   //////////
  /// analysis level: 0: only mapping; 1: 2D tracking; 2: PID;
  a->SetAnaLevel(fAnaLevel);
  a->SetIs3DTracking(fIs3DTrk); // 3D tracking for postMagTrack
  a->AddDrawEvent(c["evDraw"].as<vector<int>>()); // add events to be drawn
  a->AddDrawEvent(fDrawVec);
} // end member function ConfigAnalyzer

void ETFParaManager::PrintUserInput(){
  // cout << " -------------------------------------------- " << endl;
  cout << " ---------------- USER INPUT ---------------- " << endl;
  cout << "Using Config YAML File in \033[32;1m" << configdir() << "\033[0m\n";
  printf("Event index range: [ev0, ev1): \033[32;1m[%d, %d)\033[0m\n", fIndex0, fIndex1);
  cout << "Analysis level: \033[32;1m" << fAnaLevel << ". ";
  if(fAnaLevel <= -1) cout << "only unpacking" << "\033[0m\n";
  if(fAnaLevel <= 0) cout << "2ndary beam PID" << "\033[0m\n";
  if(fAnaLevel == 1) cout << "2D Tracking" << "\033[0m\n";
  if(fAnaLevel == 2) cout << "2D Tracking + PID" << "\033[0m\n";
  if(fAnaLevel >= 1){
    cout << "3D Tracking: \033[32;1m";
    cout << (fIs3DTrk ? "ON" : "OFF") << "\033[0m\n";
  } // end if
  cout << "Magnetic intensity: " << "\033[32;1m" << fMagB << "\033[0m Tesla" << endl;
  cout << "Event Drawn List (cmdline input): \033[32;1m";
  for(int i : fDrawVec) cout << '\t' << i;
  cout << "\033[0m\n";
  cout << " -------------- END USER INPUT -------------- " << endl;
  // cout << " -------------------------------------------- " << endl;
} // end member function PrintUserInput

const string &ETFParaManager::datafile(int i) const{
  if(i >= int(fDataFiles.size()))
    er("ETFParaManager", "datafile: requested %d out of border", i);
  return fDataFiles[i];
} // end member function datafile

// beam extraction ends
void ETFParaManager::SetReset(){
  fIsReset = true;
  ETFMsg::Info("ETFParaManager", "--- SetReset: Beam Extraction Ends ---");
} // end member function SetReset

const TH1F *ETFParaManager::GetRTHist(const string &det) const{
  if(fRTFile && fRTFile->Get(det.data()))
    return dynamic_cast<const TH1F *>(fRTFile->Get(det.data()));
  return nullptr;
} // end member function GetRTHist
